Skip to content

feat(story-2.1): Middleware protocol, Next type, and chain composition#8

Merged
lesnik512 merged 7 commits into
mainfrom
story/2-1-middleware-protocol-and-chain
May 31, 2026
Merged

feat(story-2.1): Middleware protocol, Next type, and chain composition#8
lesnik512 merged 7 commits into
mainfrom
story/2-1-middleware-protocol-and-chain

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Summary

  • Adds the AsyncClient ↔ Middleware seam (Seam 2): Middleware runtime-checkable Protocol with async def __call__(self, request: Request, next: Next) -> Response, and Next = Callable[[Request], Awaitable[Response]] exported at both httpware.middleware.* and httpware.*.
  • Adds the private compose(middlewares, transport) -> Next at httpware._internal.chain. Recursive closure fold; transport.__call__ is the bottom of the chain; empty list returns transport.__call__ directly. No try/except in compose or _wrapasyncio.CancelledError and user-raised exceptions propagate untouched (NFR15).
  • 14 tests cover ordering (outer→inner onion), short-circuit, request and response transformation, exception propagation through middleware and transport, cancellation, runtime_checkable isinstance, the Next alias's resolved type, package-root re-export, and reusability of the composed Next.

Out of scope (subsequent stories): phase decorators (2-2), Request immutability helpers beyond what already exists (2-3), auth coercion (2-4), AsyncClient wiring (2-5), streaming chain (4-3).

Spec + plan: docs/superpowers/specs/2026-05-31-middleware-protocol-and-chain-design.md, docs/superpowers/plans/2026-05-31-middleware-protocol-and-chain-plan.md.

Test plan

  • just test — 174 passed, 1 deselected (perf), 100% line coverage on the new modules.
  • just lint-cieof-fixer, ruff format --check, ruff check --no-fix, ty check all clean.
  • tests/test_no_httpx2_leakage.py passes — no `httpx2` import added.
  • from httpware import Middleware, Next and from httpware.middleware import Middleware, Next both resolve.
  • CI green on all matrix entries.

🤖 Generated with Claude Code

lesnik512 and others added 7 commits May 31, 2026 14:05
Ports the archived Middleware Execution Model decision forward to
the superpowers flow. Decisions: runtime-checkable Protocol matching
Transport / ResponseDecoder; TypeAlias for Next (3.11 floor rules out
PEP 695); private compose() at _internal/chain.py using a recursive
closure fold with transport.__call__ as the bottom of the chain;
Sequence[Middleware] over list; no exception handling in compose so
CancelledError propagates naturally; public exports at both
httpware.middleware.* and httpware.* (matches Request/Response).

Strict epic boundary — decorators (2-2), Request helpers (2-3), auth
coercion (2-4), AsyncClient wiring (2-5), and streaming chain (4-3)
land as their own stories.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds src/httpware/middleware/__init__.py defining:
- Next: TypeAlias = Callable[[Request], Awaitable[Response]]
- Middleware: @runtime_checkable Protocol with async __call__(request, next)

Matches Transport and ResponseDecoder shape. The `next` parameter
shadows the Python builtin (standard for this pattern); structural
typing matches by position, so concrete middleware may rename it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…eware cases

Adds src/httpware/_internal/chain.compose(middlewares, transport) -> Next
using a recursive closure fold. Bottom of chain is transport.__call__
(bound method, no wrapper). Empty sequence returns transport.__call__
directly. Tests verify both cases against a minimal _OkTransport fixture.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds three tests verifying the onion-execution order (outer→inner→
transport→inner→outer), request mutation via with_header propagates to
the inner middleware, and outer middleware can return a modified
Response after awaiting next. No production code changes; the existing
compose() implementation handles all three cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…reusability

Adds five tests covering the remaining acceptance criteria:
- short-circuit middleware bypasses inner layers and the transport
- exceptions raised inside middleware bubble through unchanged
- exceptions raised by the transport pass through middleware unchanged
- asyncio.CancelledError propagates (NFR15)
- the Next returned by compose can be reused across sequential requests

No production code changes; compose's no-try/except design carries
the cancellation guarantee.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds Middleware and Next to httpware/__init__.py imports and __all__
so consumers can `from httpware import Middleware, Next` in addition
to the subpackage path. Matches the existing Request/Response/Transport
re-export pattern. CHANGELOG records the Story 2.1 surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lesnik512 lesnik512 merged commit 2b70dd9 into main May 31, 2026
9 of 10 checks passed
@lesnik512 lesnik512 deleted the story/2-1-middleware-protocol-and-chain branch May 31, 2026 11:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant